package com.toobigtofail
{
	import flash.events.Event;
	import flash.utils.Dictionary;
	
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.Sfx;
	import net.flashpunk.World;
	import net.flashpunk.graphics.Backdrop;
	import net.flashpunk.graphics.Image;
	
	import realtimelib.Action;
	import realtimelib.P2PGame;
		
	public class Game extends World
	{	
		[Embed(source="/assets/sfx/snakosplaystart.mp3")] private const STARTPLAYSONG:Class;
		[Embed(source="/assets/sfx/snakos1.mp3")] private const PLAYMUSIC1:Class;
		[Embed(source="/assets/sfx/snakos2.mp3")] private const PLAYMUSIC2:Class;
		[Embed(source="/assets/sfx/snakos3.mp3")] private const PLAYMUSIC3:Class;
		[Embed(source="/assets/sfx/snakos4.mp3")] private const PLAYMUSIC4:Class;
		[Embed(source="/assets/sfx/deathsong.mp3")] private const DEATHMUSIC:Class;
		
		private const SERVER:String = "rtmfp://p2p.rtmfp.net/"; // standard adobe server
		private const DEVKEY:String = "20ef1d6c91cb3c35f7770277-abd5556700d3"; // troy's cirrus dev key

		private static const SLOW_TIME:Number = 5;
		private var _slowTimer:Number = SLOW_TIME;
		public var _isSlow:Boolean = false;
		
		public const FASTBEATSPEED:Number = .5;
		public const SLOWBEATSPEED:Number = 1;
		private const SCROLL_FACTOR:Number = .7;
		
		private var beatScript:BeatScript = new BeatScript();
		
		public var ouroboros:Ouroboros = new Ouroboros();
		public var background:Background = new Background();
		
		private const SECTIONS_REQUEST_TIMEOUT:Number = 3000; // 3 seconds
		
		private  var _p2pGame:P2PGame = new P2PGame(SERVER+DEVKEY);
		// TODO: need some way for a user to choose their group.
		//
		// FOR NOW - Wanna test, and don't wanna play with me? Change the "1" below to anything else and play your own damn game. See if I care!
		public   var actionHandler:ActionHandler = new ActionHandler(_p2pGame, "2", receiveAction);
		
		public var _player:Player = new Player(FP.screen.width/2, FP.screen.height/2);
		private var _timeSinceBeat:Number = 0;
		private var beatSpeed:Number = FASTBEATSPEED;
		
		private var _sectionNum:int = -1;
		private var _requestSectionsTime:Number = 0;
		
		private var _connected:Boolean = false;
		
		public var display:Entity = new Entity(0,0);
		
		public var _playMusic:Array = new Array(
				new Sfx(PLAYMUSIC1, playNewSong),
				new Sfx(PLAYMUSIC2, playNewSong),
				new Sfx(PLAYMUSIC3, playNewSong),
				new Sfx(PLAYMUSIC4, playNewSong)
				);
		public var _startSong:Sfx = new Sfx(STARTPLAYSONG, playNewSong);
		public var _deathSong:Sfx = new Sfx(DEATHMUSIC);
		
		
		public function Game(groupName:String) {
			super();
			_p2pGame.addEventListener(Event.CONNECT, onGameConnect);
			actionHandler.groupName = groupName;
		}
		
		override public function begin():void {
			var usr:String = "user"+(Math.round(Math.random()*10000));
			_p2pGame.connect(usr);
			
			add(background);
			ouroboros.layer = -100;
			add(ouroboros);
			add(_player);
			display.setHitbox(FP.screen.width, FP.screen.height);
			add(display);
			_startSong.play();

			
		}
		
		override public function update():void {
			super.update();
			updatePlaying();
		}
		
		private function updatePlaying():void
		{
			
			//speed and beat handler
			_timeSinceBeat += FP.elapsed;
		
			var e:Array = new Array();
			FP.world.getType("enemy",e);
			var onScreen:Boolean = true;
			for each(var ent:Entity in e)
			{					
				if(ent.x > FP.width - 150)
				{
					onScreen = false;
					
				}
			}
			if (_player.iAmHead && !_player.isDead) {
				
				if(onScreen)
				{
					var a:uint = FP.rand(beatScript.EASYRANGE);
					for each(var obj:Object in beatScript.patterns[a])
					{
						add(new BaseEnemy(obj.type, obj.x, obj.y, _isSlow ? BaseEnemy.SLOW_SPEED : BaseEnemy.FAST_SPEED, _player.iAmHead));
					}
				}
				
				if (_isSlow) {
					_slowTimer -= FP.elapsed;
					if (_slowTimer <= 0) {
						updateEnemyData(false,true);
						_isSlow = false;
						_slowTimer = SLOW_TIME;
					}
				}
			}
			
			if (_connected && -1 == _sectionNum) {
				actionHandler.sendAction(GameAction.REQUEST_SECTION_NUM);
				if ((now - _requestSectionsTime) > SECTIONS_REQUEST_TIMEOUT) {
					_sectionNum = _player.getFirstUnusedSection();
					if (_sectionNum > -1) {
						_player.assignSection(_sectionNum, _p2pGame.myUser.id);
						actionHandler.sendAction(GameAction.SEND_SECTION_NUM, {sectionNum: _sectionNum});
					}
				}
			}

		}
		
		private function playNewSong():void
		{
			_playMusic[FP.rand(_playMusic.length)].play();
		}
		
		/** Event Handlers **/
		
		/**
		 * There can be a delay between connecting and actually being connected. This method is a callback
		 * that is called when the server responds, telling us we're connected. In which case, set the global
		 * property that let's the game start.
		 */
		private function onGameConnect(event:Event):void{
			_connected = true;
			actionHandler.sendAction(GameAction.REQUEST_SECTION_NUM);
			_requestSectionsTime = now;
			
			_player.myPeerID = _p2pGame.myUser.id;
		}
		
		/**
		 * This function is the crux of the multiplayer coordination. Any action should be defined in GameActions, and
		 * each player should send an action when they do something or something happens to them. All other players will
		 * receive that action and update their state accordingly.
		 * 
		 * For example, player A will tell all other players when it shoots. Other players will receive that action and
		 * start the animation for a bullet coming from player A's avatar. If player A's bullet touches player B on his screen,
		 * player B will send out a "I'm hit" action, in which case player A can augment his score (or whatever).  
		 */
		protected function receiveAction(peerID:String, action:Action):void {
			var details:* = action.details;
			var be:BaseEnemy;
			switch(action.type) {
				case GameAction.REQUEST_SECTION_NUM:
					if (_sectionNum > -1) {
						actionHandler.sendAction(GameAction.SEND_SECTION_NUM, {sectionNum: _sectionNum});
					}
					break;
				case GameAction.SEND_SECTION_NUM:
					// Another player sent their section number
					_player.assignSection(details.sectionNum, peerID);
					break;
				case GameAction.UPDATE_POSITION:
					_player.x = details.x;
					_player.y = details.y;
					_player.dx = details.dx;
					_player.dy = details.dy;
					break;
				case GameAction.UPDATE_ENEMIES:
					var enemies:Array = new Array;
					getType("enemy", enemies);
					
					var knownEnemies:Dictionary = new Dictionary;
					for each (var e:BaseEnemy in enemies) {
						knownEnemies[e.p2pId] = e;
					}
					
					if (details.unslow && details.unslow == 1) {
						ouroboros.isSlow = false;
					}
					
					for each (var obj:* in details.enemyData) {
						if (null == knownEnemies[obj.p2pId]) {
							be = new BaseEnemy(obj.type, obj.x, obj.y, obj.speed, false);
							be.p2pId = obj.p2pId;
							be.x = obj.x;
							be.y = obj.y;
							be.deceleration = obj.deceleration;
							add(be);
						} else {
							be = (knownEnemies[obj.p2pId] as BaseEnemy);
							be.x = obj.x;
							be.y = obj.y;
							be.speed = obj.speed;
							be.p2pId = obj.p2pId;
							be.deceleration = obj.deceleration;
						}
					}
					break;
				case GameAction.ADD_ENEMY:
					be = new BaseEnemy(details.type, details.x, details.y, details.speed, false);
					be.p2pId = details.p2pId;
					be.x = details.x;
					be.y = details.y;
					be.deceleration = details.deceleration;
					add(be);
					break;
				case GameAction.SLOW_TIME:
					if (!_isSlow) {
						ouroboros.isSlow = true;
						updateEnemyData(true);
					}
					break;
				case GameAction.DIE:
					_player.die();
					break;
				case GameAction.RESTART:
					background.speed = _isSlow ? BaseEnemy.SLOW_SPEED : BaseEnemy.FAST_SPEED;
					background.deceleration = 0;
					_player.restart();
					break;
				case GameAction.MAKE_TINY:
					_player.makeTiny();
					break;
				case GameAction.MAKE_NORMAL:
					_player.makeNormal();
					break;
				case GameAction.MAKE_PRECISION:
					_player._isPrecision = true;
					break;
				case GameAction.MAKE_PRECISION_NORMAL:
					_player._isPrecision = false;
					break;
			}
		}
		
		public function updateEnemyData(doSlow:Boolean=false, doUnslow:Boolean=false, doDecelerate:Boolean=false):void {
			// Send all enemy information to other players
			var e:Array = new Array;
			FP.world.getType("enemy",e);
			
			var enemyData:Array = new Array;
			for each (var enemy:BaseEnemy in e) {
				if (doSlow) {
					enemy.speed = BaseEnemy.SLOW_SPEED;
					background.speed = BaseEnemy.SLOW_SPEED;
					_isSlow = true;
				} else if (doUnslow) {
					enemy.speed = BaseEnemy.FAST_SPEED;
					ouroboros.isSlow = false;
					background.speed = BaseEnemy.FAST_SPEED;
					_isSlow = false;
				}
				
				if (doDecelerate) {
					enemy.deceleration = BaseEnemy.DEATH_DECELERATION;
					background.deceleration = BaseEnemy.DEATH_DECELERATION;
				}
				enemyData.push({p2pId: enemy.p2pId, x: enemy.x, y: enemy.y, type: enemy.enemyType, speed: enemy.speed, deceleration: enemy.deceleration});
			}
			actionHandler.sendAction(GameAction.UPDATE_ENEMIES, {enemyData: enemyData, unslow: (doUnslow ? 1 : 0)});
		}
		
		private function get now():Number { return (new Date()).getTime(); }
		public function get isSlow():Boolean { return _isSlow; }
	}
}